#include <proc.h>
#include <kmalloc.h>
#include <string.h>
#include <sync.h>
#include <pmm.h>
#include <error.h>
#include <sched.h>
#include <elf.h>
#include <vmm.h>
#include <trap.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>

/* ------------- process/thread mechanism design&implementation -------------
(an simplified Linux process/thread mechanism )
introduction:
  ucore implements a simple process/thread mechanism. process contains the independent memory sapce, at least one threads
for execution, the kernel data(for management), processor state (for context switch), files(in lab6), etc. ucore needs to
manage all these details efficiently. In ucore, a thread is just a special kind of process(share process's memory).
------------------------------
process state       :     meaning               -- reason
    PROC_UNINIT     :   uninitialized           -- alloc_proc
    PROC_SLEEPING   :   sleeping                -- try_free_pages, do_wait, do_sleep
    PROC_RUNNABLE   :   runnable(maybe running) -- proc_init, wakeup_proc, 
    PROC_ZOMBIE     :   almost dead             -- do_exit

-----------------------------
process state changing:
                                            
  alloc_proc                                 RUNNING
      +                                   +--<----<--+
      +                                   + proc_run +
      V                                   +-->---->--+ 
PROC_UNINIT -- proc_init/wakeup_proc --> PROC_RUNNABLE -- try_free_pages/do_wait/do_sleep --> PROC_SLEEPING --
                                           A      +                                                           +
                                           |      +--- do_exit --> PROC_ZOMBIE                                +
                                           +                                                                  + 
                                           -----------------------wakeup_proc----------------------------------
-----------------------------
process relations
parent:           proc->parent  (proc is children)
children:         proc->cptr    (proc is parent)
older sibling:    proc->optr    (proc is younger sibling)
younger sibling:  proc->yptr    (proc is older sibling)
-----------------------------
related syscall for process:
SYS_exit        : process exit,                           -->do_exit
SYS_fork        : create child process, dup mm            -->do_fork-->wakeup_proc
SYS_wait        : wait process                            -->do_wait
SYS_exec        : after fork, process execute a program   -->load a program and refresh the mm
SYS_clone       : create child thread                     -->do_fork-->wakeup_proc
SYS_yield       : process flag itself need resecheduling, -- proc->need_sched=1, then scheduler will rescheule this process
SYS_sleep       : process sleep                           -->do_sleep 
SYS_kill        : kill process                            -->do_kill-->proc->flags |= PF_EXITING
                                                                 -->wakeup_proc-->do_wait-->do_exit   
SYS_getpid      : get the process's pid

*/

// the process set's list
list_entry_t proc_list;

#define HASH_SHIFT          10
#define HASH_LIST_SIZE      (1 << HASH_SHIFT)
#define pid_hashfn(x)       (hash32(x, HASH_SHIFT))

// has list for process set based on pid
static list_entry_t hash_list[HASH_LIST_SIZE];

// idle proc
struct proc_struct *idleproc = NULL;
// init proc
struct proc_struct *initproc = NULL;
// current proc
struct proc_struct *current = NULL;

static int nr_process = 0;

void kernel_thread_entry(void);
void forkrets(struct trapframe *tf);
void switch_to(struct context *from, struct context *to);

// alloc_proc - alloc a proc_struct and init all fields of proc_struct
static struct proc_struct *
alloc_proc(void) {
    struct proc_struct *proc = kmalloc(sizeof(struct proc_struct));
    if (proc != NULL) {
    //LAB4:EXERCISE1 YOUR CODE
    /*
     * below fields in proc_struct need to be initialized
     *       enum proc_state state;                      // Process state
     *       int pid;                                    // Process ID
     *       int runs;                                   // the running times of Proces
     *       uintptr_t kstack;                           // Process kernel stack
     *       volatile bool need_resched;                 // bool value: need to be rescheduled to release CPU?
     *       struct proc_struct *parent;                 // the parent process
     *       struct mm_struct *mm;                       // Process's memory management field
     *       struct context context;                     // Switch here to run process
     *       struct trapframe *tf;                       // Trap frame for current interrupt
     *       uintptr_t cr3;                              // CR3 register: the base addr of Page Directroy Table(PDT)
     *       uint32_t flags;                             // Process flag
     *       char name[PROC_NAME_LEN + 1];               // Process name
     */
     //LAB5 YOUR CODE : (update LAB4 steps)
    /*
     * below fields(add in LAB5) in proc_struct need to be initialized	
     *       uint32_t wait_state;                        // waiting state
     *       struct proc_struct *cptr, *yptr, *optr;     // relations between processes
	 */
        proc->state = PROC_UNINIT; // 初始状态为未初始化
        proc->pid = -1;            // 初始进程ID为-1
        proc->runs = 0;            // 初始运行次数为0
        proc->kstack = 0;          // 初始内核栈指针为0
        proc->need_resched = 0;    // 初始不需要重新调度
        proc->parent = NULL;       // 初始父进程指针为NULL
        proc->mm = NULL;           // 初始内存管理结构指针为NULL
        memset(&proc->context, 0, sizeof(struct context));  // 初始化上下文切换信息
        proc->tf = NULL;           // 初始中断陷阱帧指针为NULL
        proc->cr3 = boot_cr3;      // 初始CR3寄存器值为0
        proc->flags = 0;           // 初始进程标志为0
        memset(proc->name, 0, PROC_NAME_LEN);   // 初始进程名称为空字符串
        proc->wait_state = 0;
        proc->cptr = proc->optr = proc->yptr = NULL;
    }
    return proc;
}

// set_proc_name - set the name of proc
/**
 * 设置进程名称
 * 
 * 本函数旨在为进程结构体中的名称字段赋予新的值它首先清空进程名称字段中的现有内容，
 * 然后将新的名称复制到该字段中
 * 
 * @param proc 指向进程结构体的指针，该结构体包含进程的相关信息包括进程名称
 * @param name 指向新的进程名称的字符串
 * 
 * @return 返回指向进程名称的指针，这是在内存中直接修改后的结果
 * 
 * 注意：此函数直接操作传入的进程结构体，对名称字段进行先清空后赋值的操作
 */
char *
set_proc_name(struct proc_struct *proc, const char *name) {
    
    // 清空进程名称字段中的现有内容，以准备存储新的名称
    memset(proc->name, 0, sizeof(proc->name));
    // 将新的名称复制到进程名称字段中
    return memcpy(proc->name, name, PROC_NAME_LEN);
}

// get_proc_name - get the name of proc
char *
get_proc_name(struct proc_struct *proc) {
    static char name[PROC_NAME_LEN + 1];
    memset(name, 0, sizeof(name));
    return memcpy(name, proc->name, PROC_NAME_LEN);
}

// set_links - set the relation links of process
static void
set_links(struct proc_struct *proc) {
    list_add(&proc_list, &(proc->list_link));
    proc->yptr = NULL;
    if ((proc->optr = proc->parent->cptr) != NULL) {
        proc->optr->yptr = proc;
    }
    proc->parent->cptr = proc;
    nr_process ++;
}

// remove_links - clean the relation links of process
static void
remove_links(struct proc_struct *proc) {
    list_del(&(proc->list_link));
    if (proc->optr != NULL) {
        proc->optr->yptr = proc->yptr;
    }
    if (proc->yptr != NULL) {
        proc->yptr->optr = proc->optr;
    }
    else {
       proc->parent->cptr = proc->optr;
    }
    nr_process --;
}

// get_pid - alloc a unique pid for process
/**
 * 安全地获取下一个进程ID。
 * 
 * 此函数通过遍历进程列表来寻找一个未被使用的进程ID，以确保新分配的ID不会与现有进程冲突。
 * 它使用静态变量来跟踪最后一个分配的PID和下一个安全PID，以提高效率并避免PID冲突。
 * 
 * @return 返回一个未被使用的进程ID。
 */
static int
get_pid(void) {
    // 确保PID的最大值大于系统中允许的最大进程数，以避免PID不足的情况。
    static_assert(MAX_PID > MAX_PROCESS);
    
    // 定义一个指向进程结构的指针，用于遍历进程列表。
    struct proc_struct *proc;
    // 初始化列表指针，从进程列表的头部开始。
    list_entry_t *list = &proc_list, *le;
    // 定义静态变量，next_safe用于记录下一个安全的PID值，last_pid用于记录上一个分配的PID。
    static int next_safe = MAX_PID, last_pid = MAX_PID;

    // 尝试递增last_pid以查找下一个可用的PID，如果超过最大值则重置为1。
    if (++ last_pid >= MAX_PID) {
        last_pid = 1;
        goto inside;
    }

     // 如果当前的last_pid大于等于next_safe，表示需要重新计算下一个安全的PID。
    if (last_pid >= next_safe) {
    inside:
        next_safe = MAX_PID;
    repeat:
        // 从进程列表的头部开始遍历。
        le = list;
        while ((le = list_next(le)) != list) {

            // 将列表项转换为进程结构。
            proc = le2proc(le, list_link);

        // 如果找到相同PID的进程，表示当前last_pid已被使用，需要继续寻找下一个可用的PID。
            if (proc->pid == last_pid) {
                if (++ last_pid >= next_safe) {
                    if (last_pid >= MAX_PID) {
                        last_pid = 1;
                    }
                    next_safe = MAX_PID;
                    goto repeat;
                }
            }

            // 如果找到一个更大的PID，更新next_safe为当前进程的PID，以确保找到的PID是安全的。
            else if (proc->pid > last_pid && next_safe > proc->pid) {
                next_safe = proc->pid;
            }
        }
    }
    // 返回找到的可用PID。
    return last_pid;
}

// proc_run - make process "proc" running on cpu
// NOTE: before call switch_to, should load  base addr of "proc"'s new PDT
//切换当前运行的进程
void
proc_run(struct proc_struct *proc) {
    if (proc != current) {
        bool intr_flag;
        struct proc_struct *prev = current, *next = proc;
        local_intr_save(intr_flag);
        {
            current = proc;
            load_esp0(next->kstack + KSTACKSIZE);
            lcr3(next->cr3);
            switch_to(&(prev->context), &(next->context));
        }
        local_intr_restore(intr_flag);
    }
}

// forkret -- the first kernel entry point of a new thread/process
// NOTE: the addr of forkret is setted in copy_thread function
//       after switch_to, the current proc will execute here.
// 函数在调用时会传递当前进程的线程上下文（通过 current->tf 获取）给 forkrets 函数。
static void
forkret(void) {
    forkrets(current->tf);
}

// hash_proc - add proc into proc hash_list
/**
 * 将进程结构体添加到哈希表中
 * 
 * 当需要在哈希表中添加一个进程时，调用此函数将进程结构体中的哈希链接添加到相应的哈希表链表中
 * 这有助于在需要时快速查找进程
 * 
 * @param proc 指向进程结构体的指针，表示要添加到哈希表的进程
 */
static void
hash_proc(struct proc_struct *proc) {
    // 根据进程的PID计算哈希值，并将进程添加到相应哈希链表的末尾
    list_add(hash_list + pid_hashfn(proc->pid), &(proc->hash_link));
}

// unhash_proc - delete proc from proc hash_list
static void
unhash_proc(struct proc_struct *proc) {
    list_del(&(proc->hash_link));
}

// find_proc - find proc frome proc hash_list according to pid
struct proc_struct *
find_proc(int pid) {
    if (0 < pid && pid < MAX_PID) {
        list_entry_t *list = hash_list + pid_hashfn(pid), *le = list;
        while ((le = list_next(le)) != list) {
            struct proc_struct *proc = le2proc(le, hash_link);
            if (proc->pid == pid) {
                return proc;
            }
        }
    }
    return NULL;
}

// kernel_thread - create a kernel thread using "fn" function
// NOTE: the contents of temp trapframe tf will be copied to 
//       proc->tf in do_fork-->copy_thread function
/**
 * 创建一个内核线程
 * 
 * @param fn 指向线程要执行的函数的指针
 * @param arg 传递给fn函数的参数
 * @param clone_flags 克隆标志，用于指定线程创建的细节
 * 
 * @return 返回线程创建的结果，成功则为线程ID，失败则为错误码
 * 
 * 该函数通过设置trapframe来创建一个内核线程，并安排其执行指定的函数fn
 * 使用do_fork函数来进行实际的线程创建操作，创建的线程将共享父线程的虚拟内存
 */
int
kernel_thread(int (*fn)(void *), void *arg, uint32_t clone_flags) {
    // 初始化trapframe结构体，用于描述线程的初始状态
    struct trapframe tf;
    memset(&tf, 0, sizeof(struct trapframe));

    // 设置代码段和数据段寄存器的值，使线程运行在内核态
    tf.tf_cs = KERNEL_CS;
    tf.tf_ds = tf.tf_es = tf.tf_ss = KERNEL_DS;

    // 将要执行的函数fn的地址和参数arg的地址分别放入ebx和edx寄存器
    tf.tf_regs.reg_ebx = (uint32_t)fn;
    tf.tf_regs.reg_edx = (uint32_t)arg;

    // 设置指令指针寄存器eip，使其指向内核线程入口函数kernel_thread_entry
    tf.tf_eip = (uint32_t)kernel_thread_entry;

    // 调用do_fork函数创建新线程，新线程将共享父线程的虚拟内存
    // 并将新线程的初始状态设置为之前准备的trapframe
    return do_fork(clone_flags | CLONE_VM, 0, &tf);
}

// setup_kstack - alloc pages with size KSTACKPAGE as process kernel stack
/**
 * 设置进程的内核栈
 * 
 * @param proc 指向进程结构的指针，用于初始化该进程的内核栈
 * @return 0表示成功，-E_NO_MEM表示内存分配失败
 * 
 * 此函数通过分配一页内存用作进程的内核栈，并将该内存页的虚拟地址设置为进程的内核栈地址
 * 如果内存分配成功，则返回0；如果内存分配失败，则返回-E_NO_MEM
 */
static int
setup_kstack(struct proc_struct *proc) {
    // 分配KSTACKPAGE页内存用作内核栈
    struct Page *page = alloc_pages(KSTACKPAGE);
    if (page != NULL) {
        // 如果内存分配成功，将内存页的虚拟地址设置为进程的内核栈地址
        proc->kstack = (uintptr_t)page2kva(page);
        return 0;
    }
    // 如果内存分配失败，返回错误码
    return -E_NO_MEM;
}

// put_kstack - free the memory space of process kernel stack
static void
put_kstack(struct proc_struct *proc) {
    free_pages(kva2page((void *)(proc->kstack)), KSTACKPAGE);
}

// setup_pgdir - alloc one page as PDT
static int
setup_pgdir(struct mm_struct *mm) {
    struct Page *page;
    if ((page = alloc_page()) == NULL) {
        return -E_NO_MEM;
    }
    pde_t *pgdir = page2kva(page);
    memcpy(pgdir, boot_pgdir, PGSIZE);
    pgdir[PDX(VPT)] = PADDR(pgdir) | PTE_P | PTE_W;
    mm->pgdir = pgdir;
    return 0;
}

// put_pgdir - free the memory space of PDT
static void
put_pgdir(struct mm_struct *mm) {
    free_page(kva2page(mm->pgdir));
}

// copy_mm - process "proc" duplicate OR share process "current"'s mm according clone_flags
//         - if clone_flags & CLONE_VM, then "share" ; else "duplicate"
static int
copy_mm(uint32_t clone_flags, struct proc_struct *proc) {
    struct mm_struct *mm, *oldmm = current->mm;

    /* current is a kernel thread */
    if (oldmm == NULL) {
        return 0;
    }
    if (clone_flags & CLONE_VM) {
        mm = oldmm;
        goto good_mm;
    }

    int ret = -E_NO_MEM;
    if ((mm = mm_create()) == NULL) {
        goto bad_mm;
    }
    if (setup_pgdir(mm) != 0) {
        goto bad_pgdir_cleanup_mm;
    }

    lock_mm(oldmm);
    {
        ret = dup_mmap(mm, oldmm);
    }
    unlock_mm(oldmm);

    if (ret != 0) {
        goto bad_dup_cleanup_mmap;
    }

good_mm:
    mm_count_inc(mm);
    proc->mm = mm;
    proc->cr3 = PADDR(mm->pgdir);
    return 0;
bad_dup_cleanup_mmap:
    exit_mmap(mm);
    put_pgdir(mm);
bad_pgdir_cleanup_mm:
    mm_destroy(mm);
bad_mm:
    return ret;
}

// copy_thread - setup the trapframe on the  process's kernel stack top and
//             - setup the kernel entry point and stack of process
/**
 * 复制当前线程的上下文到新进程。
 * 
 * 该函数初始化新进程的陷阱帧和上下文，使其作为当前线程的子进程执行。主要操作包括设置初始执行环境、栈指针，并启用子进程的中断标志。
 * 
 * @param proc 指向新进程的进程结构体，用于存储初始化后的陷阱帧和上下文信息。
 * @param esp 新进程的栈指针，表示新进程栈的初始位置。
 * @param tf 指向当前线程的陷阱帧结构体，用于将当前线程的执行状态复制到新进程中。
 */
static void
copy_thread(struct proc_struct *proc, uintptr_t esp, struct trapframe *tf) {
    // 初始化新进程的陷阱帧，位于其内核栈的顶部
    proc->tf = (struct trapframe *)(proc->kstack + KSTACKSIZE) - 1;

    // 将当前线程的陷阱帧内容复制到新进程的陷阱帧
    *(proc->tf) = *tf;

    // 设置子进程的返回值为0，表示fork成功
    proc->tf->tf_regs.reg_eax = 0;
    // 设置子进程的栈指针为指定的esp位置
    proc->tf->tf_esp = esp;
     // 启用子进程的中断标志
    proc->tf->tf_eflags |= FL_IF;

    // 设置子进程的初始指令指针为forkret函数的地址
    proc->context.eip = (uintptr_t)forkret;
    // 设置子进程的栈指针为其陷阱帧的地址
    proc->context.esp = (uintptr_t)(proc->tf);
}

/* do_fork -     parent process for a new child process
 * @clone_flags: used to guide how to clone the child process
 * @stack:       the parent's user stack pointer. if stack==0, It means to fork a kernel thread.
 * @tf:          the trapframe info, which will be copied to child process's proc->tf
 */
/**
 * do_fork - 创建一个新进程，这是进程创建的核心函数
 * @clone_flags: 克隆标志，用于确定新进程如何共享资源
 * @stack: 新进程的用户栈指针
 * @tf: 新进程的trapframe结构体指针，用于设置新进程的执行环境
 * 
 * 返回值: 新进程的pid，如果创建失败则返回错误码
 * 
 * 此函数负责分配proc_struct、设置内核栈、复制或共享内存管理结构，
 * 复制线程上下文，将新进程插入到进程列表中，并将其设置为可运行状态
 */
int
do_fork(uint32_t clone_flags, uintptr_t stack, struct trapframe *tf) {
    int ret = -E_NO_FREE_PROC;
    struct proc_struct *proc;
    // 检查系统中是否有足够的资源创建新进程
    if (nr_process >= MAX_PROCESS) {
        goto fork_out;
    }
    ret = -E_NO_MEM;
    //LAB4:EXERCISE2 YOUR CODE
    /*
     * Some Useful MACROs, Functions and DEFINEs, you can use them in below implementation.
     * MACROs or Functions:
     *   alloc_proc:   create a proc struct and init fields (lab4:exercise1)
     *   setup_kstack: alloc pages with size KSTACKPAGE as process kernel stack
     *   copy_mm:      process "proc" duplicate OR share process "current"'s mm according clone_flags
     *                 if clone_flags & CLONE_VM, then "share" ; else "duplicate"
     *   copy_thread:  setup the trapframe on the  process's kernel stack top and
     *                 setup the kernel entry point and stack of process
     *   hash_proc:    add proc into proc hash_list
     *   get_pid:      alloc a unique pid for process
     *   wakeup_proc:  set proc->state = PROC_RUNNABLE
     * VARIABLES:
     *   proc_list:    the process set's list
     *   nr_process:   the number of process set
     */

    //    1. call alloc_proc to allocate a proc_struct
    //    2. call setup_kstack to allocate a kernel stack for child process
    //    3. call copy_mm to dup OR share mm according clone_flag
    //    4. call copy_thread to setup tf & context in proc_struct
    //    5. insert proc_struct into hash_list && proc_list
    //    6. call wakeup_proc to make the new child process RUNNABLE
    //    7. set ret vaule using child proc's pid
    //调用alloc_proc，首先获得一块用户信息块
    if((proc = alloc_proc()) == NULL){
        goto fork_out;
    }

     // 设置新进程的父进程为当前进程，并确保当前进程的wait_state为0
    proc->parent = current;
    assert(current->wait_state == 0);

    // 为新进程分配内核栈
    if (setup_kstack(proc) != 0) {
        goto bad_fork_cleanup_proc;
    }
    // 根据clone_flags复制或共享内存管理结构
    if (copy_mm(clone_flags, proc) != 0) {
        goto bad_fork_cleanup_kstack;
    }

    // 复制线程上下文到新进程
    copy_thread(proc, stack, tf);

    bool intr_flag;
    local_intr_save(intr_flag);
    {
        // 为新进程分配唯一的pid，并将其插入到进程哈希列表和进程列表中
        proc->pid = get_pid();
        hash_proc(proc);
        set_links(proc);

    }
    local_intr_restore(intr_flag);

    // 将新进程设置为可运行状态
    wakeup_proc(proc);

    // 设置返回值为新进程的pid
    ret = proc->pid;
	//LAB5 YOUR CODE : (update LAB4 steps)
   /* Some Functions
    *    set_links:  set the relation links of process.  ALSO SEE: remove_links:  lean the relation links of process 
    *    -------------------
	*    update step 1: set child proc's parent to current process, make sure current process's wait_state is 0
	*    update step 5: insert proc_struct into hash_list && proc_list, set the relation links of process
    */

	
fork_out:
    return ret;

bad_fork_cleanup_kstack:
 // 如果资源分配失败，释放已分配的内核栈
    put_kstack(proc);
bad_fork_cleanup_proc:
// 释放proc_struct结构体
    kfree(proc);
    goto fork_out;
}

// do_exit - called by sys_exit
//   1. call exit_mmap & put_pgdir & mm_destroy to free the almost all memory space of process
//   2. set process' state as PROC_ZOMBIE, then call wakeup_proc(parent) to ask parent reclaim itself.
//   3. call scheduler to switch to other process
/**
 * 退出当前进程，并释放其占用的资源。
 * 
 * @param error_code 进程退出码，表示进程退出的状态。
 * 
 * @return 该函数不会返回。
 */
int
do_exit(int error_code) {
    // 检查当前进程是否为idle进程，如果是，则引发panic。
    if (current == idleproc) {
        panic("idleproc exit.\n");
    }
    // 检查当前进程是否为init进程，如果是，则引发panic。
    if (current == initproc) {
        panic("initproc exit.\n");
    }
    
    // 获取当前进程的内存管理结构。
    struct mm_struct *mm = current->mm;
    // 如果当前进程有内存管理结构，则进行清理。
    if (mm != NULL) {
        // 加载引导页表，准备清理当前进程的页表。
        lcr3(boot_cr3);
         // 如果内存管理结构的引用计数减到0，则释放相关资源。
        if (mm_count_dec(mm) == 0) {
            exit_mmap(mm);
            put_pgdir(mm);
            mm_destroy(mm);
        }
         // 将当前进程的内存管理结构设置为NULL。
        current->mm = NULL;
    }
    // 将当前进程的状态设置为僵尸状态，并保存退出码。
    current->state = PROC_ZOMBIE;
    current->exit_code = error_code;
    
     // 保存中断状态，并获取当前进程的父进程。
    bool intr_flag;
    struct proc_struct *proc;
    local_intr_save(intr_flag);
    {
        proc = current->parent;
         // 如果父进程在等待子进程，则唤醒父进程。
        if (proc->wait_state == WT_CHILD) {
            wakeup_proc(proc);
        }
          // 遍历当前进程的所有子进程，并将它们的父进程设置为init进程。
        while (current->cptr != NULL) {
            proc = current->cptr;
            current->cptr = proc->optr;
    
            proc->yptr = NULL;
            if ((proc->optr = initproc->cptr) != NULL) {
                initproc->cptr->yptr = proc;
            }
            proc->parent = initproc;
            initproc->cptr = proc;
            // 如果子进程处于僵尸状态，并且init进程在等待子进程，则唤醒init进程。
            if (proc->state == PROC_ZOMBIE) {
                if (initproc->wait_state == WT_CHILD) {
                    wakeup_proc(initproc);
                }
            }
        }
    }
     // 恢复中断状态。
    local_intr_restore(intr_flag);
     // 调度新的进程运行。
    schedule();
    // 如果调度失败，引发panic。
    panic("do_exit will not return!! %d.\n", current->pid);
}

/* load_icode - load the content of binary program(ELF format) as the new content of current process
 * @binary:  the memory addr of the content of binary program
 * @size:  the size of the content of binary program
 */
/**
 * 加载指令代码到当前进程的内存中。
 * 函数实现以下步骤：
 * (1) 为当前进程创建一个新的内存描述符 (mm_struct)。
 * (2) 创建一个新的页目录表 (PDT)，并将 mm->pgdir 设置为内核虚拟地址的 PDT。
 * (3) 将二进制程序的 TEXT/DATA 段复制到进程的内存空间，并构建 BSS 部分。
 * (4) 构建用户栈内存。
 * (5) 设置当前进程的 mm、sr3，并将 CR3 寄存器设置为页目录的物理地址。
 * (6) 为用户环境设置陷阱帧 (trapframe)。
 *
 * @param binary 二进制程序的指针
 * @param size 二进制程序的大小
 * @return 成功返回 0，失败返回负的错误码
 */
static int
load_icode(unsigned char *binary, size_t size) {
    // 检查当前进程是否已经有内存描述符，如果有则触发 panic
    if (current->mm != NULL) {
        panic("load_icode: current->mm must be empty.\n");
    }

    // 初始化返回值为 -E_NO_MEM
    int ret = -E_NO_MEM;
    struct mm_struct *mm;
    //(1) create a new mm for current process
    //(1) 为当前进程创建一个新的内存描述符 (mm_struct)
    if ((mm = mm_create()) == NULL) {
        goto bad_mm;
    }
    //(2) create a new PDT, and mm->pgdir= kernel virtual addr of PDT
     // (2) 创建一个新的页目录表 (PDT)，并将 mm->pgdir 设置为内核虚拟地址的 PDT
    if (setup_pgdir(mm) != 0) {
        goto bad_pgdir_cleanup_mm;
    }
    //(3) copy TEXT/DATA section, build BSS parts in binary to memory space of process
    // (3) 将二进制程序的 TEXT/DATA 段复制到进程的内存空间，并构建 BSS 部分
    struct Page *page;
    //(3.1) get the file header of the bianry program (ELF format)
    // (3.1) 获取二进制程序的文件头 (ELF 格式)
    struct elfhdr *elf = (struct elfhdr *)binary;
    //(3.2) get the entry of the program section headers of the bianry program (ELF format)
    // (3.2) 获取二进制程序的程序段头表入口 (ELF 格式)
    struct proghdr *ph = (struct proghdr *)(binary + elf->e_phoff);
    //(3.3) This program is valid?
    // (3.3) 检查程序是否有效
    if (elf->e_magic != ELF_MAGIC) {
        ret = -E_INVAL_ELF;
        goto bad_elf_cleanup_pgdir;
    }

    uint32_t vm_flags, perm;
    struct proghdr *ph_end = ph + elf->e_phnum;
    for (; ph < ph_end; ph ++) {
    //(3.4) find every program section headers
    // (3.4) 查找每一个程序段头
        if (ph->p_type != ELF_PT_LOAD) {
            continue ;
        }
        if (ph->p_filesz > ph->p_memsz) {
            ret = -E_INVAL_ELF;
            goto bad_cleanup_mmap;
        }
        if (ph->p_filesz == 0) {
            continue ;
        }
    //(3.5) call mm_map fun to setup the new vma ( ph->p_va, ph->p_memsz)
     // (3.5) 调用 mm_map 函数设置新的 VMA (ph->p_va, ph->p_memsz)
        vm_flags = 0, perm = PTE_U;
        if (ph->p_flags & ELF_PF_X) vm_flags |= VM_EXEC;
        if (ph->p_flags & ELF_PF_W) vm_flags |= VM_WRITE;
        if (ph->p_flags & ELF_PF_R) vm_flags |= VM_READ;
        if (vm_flags & VM_WRITE) perm |= PTE_W;
        if ((ret = mm_map(mm, ph->p_va, ph->p_memsz, vm_flags, NULL)) != 0) {
            goto bad_cleanup_mmap;
        }
        unsigned char *from = binary + ph->p_offset;
        size_t off, size;
        uintptr_t start = ph->p_va, end, la = ROUNDDOWN(start, PGSIZE);

        ret = -E_NO_MEM;

     //(3.6) alloc memory, and  copy the contents of every program section (from, from+end) to process's memory (la, la+end)
      // (3.6) 分配内存，并将每个程序段的内容 (from, from+end) 复制到进程的内存 (la, la+end)
        end = ph->p_va + ph->p_filesz;
     //(3.6.1) copy TEXT/DATA section of bianry program
     // (3.6.1) 复制二进制程序的 TEXT/DATA 段
        while (start < end) {
            if ((page = pgdir_alloc_page(mm->pgdir, la, perm)) == NULL) {
                goto bad_cleanup_mmap;
            }
            off = start - la, size = PGSIZE - off, la += PGSIZE;
            if (end < la) {
                size -= la - end;
            }
            memcpy(page2kva(page) + off, from, size);
            start += size, from += size;
        }

      //(3.6.2) build BSS section of binary program
      // (3.6.2) 构建二进制程序的 BSS 段
        end = ph->p_va + ph->p_memsz;
        if (start < la) {
            /* ph->p_memsz == ph->p_filesz */
            if (start == end) {
                continue ;
            }
            off = start + PGSIZE - la, size = PGSIZE - off;
            if (end < la) {
                size -= la - end;
            }
            memset(page2kva(page) + off, 0, size);
            start += size;
            assert((end < la && start == end) || (end >= la && start == la));
        }
        while (start < end) {
            if ((page = pgdir_alloc_page(mm->pgdir, la, perm)) == NULL) {
                goto bad_cleanup_mmap;
            }
            off = start - la, size = PGSIZE - off, la += PGSIZE;
            if (end < la) {
                size -= la - end;
            }
            memset(page2kva(page) + off, 0, size);
            start += size;
        }
    }
    //(4) build user stack memory
    // (4) 构建用户栈内存
    vm_flags = VM_READ | VM_WRITE | VM_STACK;
    if ((ret = mm_map(mm, USTACKTOP - USTACKSIZE, USTACKSIZE, vm_flags, NULL)) != 0) {
        goto bad_cleanup_mmap;
    }
    assert(pgdir_alloc_page(mm->pgdir, USTACKTOP-PGSIZE , PTE_USER) != NULL);
    assert(pgdir_alloc_page(mm->pgdir, USTACKTOP-2*PGSIZE , PTE_USER) != NULL);
    assert(pgdir_alloc_page(mm->pgdir, USTACKTOP-3*PGSIZE , PTE_USER) != NULL);
    assert(pgdir_alloc_page(mm->pgdir, USTACKTOP-4*PGSIZE , PTE_USER) != NULL);
    
    //(5) set current process's mm, sr3, and set CR3 reg = physical addr of Page Directory
    // (5) 设置当前进程的 mm、sr3，并将 CR3 寄存器设置为页目录的物理地址
    mm_count_inc(mm);
    current->mm = mm;
    current->cr3 = PADDR(mm->pgdir);
    lcr3(PADDR(mm->pgdir));

    //(6) setup trapframe for user environment
     // (6) 为用户环境设置陷阱帧 (trapframe)
    struct trapframe *tf = current->tf;
    memset(tf, 0, sizeof(struct trapframe));
    /* LAB5:EXERCISE1 YOUR CODE
     * should set tf_cs,tf_ds,tf_es,tf_ss,tf_esp,tf_eip,tf_eflags
     * NOTICE: If we set trapframe correctly, then the user level process can return to USER MODE from kernel. So
     *          tf_cs should be USER_CS segment (see memlayout.h)
     *          tf_ds=tf_es=tf_ss should be USER_DS segment
     *          tf_esp should be the top addr of user stack (USTACKTOP)
     *          tf_eip should be the entry point of this binary program (elf->e_entry)
     *          tf_eflags should be set to enable computer to produce Interrupt
     */
    tf->tf_cs = USER_CS;
    tf->tf_ds = tf->tf_es = tf->tf_ss = USER_DS;
    tf->tf_esp = USTACKTOP;
    tf->tf_eip = elf->e_entry;
    tf->tf_eflags = FL_IF;
    ret = 0;
out:
    return ret;
bad_cleanup_mmap:
    exit_mmap(mm);
bad_elf_cleanup_pgdir:
    put_pgdir(mm);
bad_pgdir_cleanup_mm:
    mm_destroy(mm);
bad_mm:
    goto out;
}

// do_execve - call exit_mmap(mm)&put_pgdir(mm) to reclaim memory space of current process
//           - call load_icode to setup new memory space accroding binary prog.
/**
 * 执行一个新程序，替换当前进程的地址空间
 * 
 * @param name 程序名称的指针
 * @param len 程序名称的长度
 * @param binary 程序二进制代码的指针
 * @param size 程序二进制代码的大小
 * 
 * 此函数首先检查程序名称是否有效，然后准备执行新的程序
 * 它会清除当前进程的地址空间，并加载新的程序代码
 * 如果加载过程中发生错误，函数将终止当前进程
 * 
 * @return 成功时返回0，失败时返回错误码
 */
int
do_execve(const char *name, size_t len, unsigned char *binary, size_t size) {
    // 获取当前进程的内存管理结构
    struct mm_struct *mm = current->mm;
    
    // 检查程序名称是否在用户空间中有效
    if (!user_mem_check(mm, (uintptr_t)name, len, 0)) {
        return -E_INVAL;
    }

    // 如果程序名称过长，截断到最大长度
    if (len > PROC_NAME_LEN) {
        len = PROC_NAME_LEN;
    }

     // 准备一个本地缓冲区，用于存储程序名称
    char local_name[PROC_NAME_LEN + 1];
    memset(local_name, 0, sizeof(local_name));
    memcpy(local_name, name, len);

    // 如果当前进程已有内存管理结构，进行清理
    if (mm != NULL) {
        // 切换到引导页表，准备清理当前进程的地址空间
        lcr3(boot_cr3);

        // 减少内存管理结构的引用计数，如果为0则进行清理
        if (mm_count_dec(mm) == 0) {
            exit_mmap(mm);
            put_pgdir(mm);
            mm_destroy(mm);
        }
        // 当前进程不再有内存管理结构
        current->mm = NULL;
    }
    // 加载新的程序代码
    int ret;
    if ((ret = load_icode(binary, size)) != 0) {
        // 如果加载失败，终止当前进程
        goto execve_exit;
    }
    // 设置当前进程的名称为新的程序名称
    set_proc_name(current, local_name);
    return 0;

execve_exit:
// 加载失败时的清理工作
    do_exit(ret);
    panic("already exit: %e.\n", ret);
}

// do_yield - ask the scheduler to reschedule
int
do_yield(void) {
    current->need_resched = 1;
    return 0;
}

// do_wait - wait one OR any children with PROC_ZOMBIE state, and free memory space of kernel stack
//         - proc struct of this child.
// NOTE: only after do_wait function, all resources of the child proces are free.
/**
 * 等待一个子进程结束并获取其退出状态码
 * 
 * @param pid 要等待的子进程的PID如果为0，则等待任何子进程
 * @param code_store 用于存储子进程退出状态码的指针如果不需要存储状态码，则传入NULL
 * @return 返回0表示成功，负值表示错误代码
 * 
 * 此函数用于父进程等待其子进程结束，并可选地获取子进程的退出状态码
 * 它首先检查参数的有效性，然后寻找指定的子进程如果找到已终止的子进程，
 * 则回收其资源并返回子进程的退出状态码如果没有找到已终止的子进程，则当前进程进入睡眠状态，
 * 直到有子进程终止或者没有子进程可等待时返回错误代码
 */
int
do_wait(int pid, int *code_store) {
    // 获取当前进程的内存描述符
    struct mm_struct *mm = current->mm;
     // 如果code_store不为NULL，检查其指向的用户内存区域是否有效
    if (code_store != NULL) {
        if (!user_mem_check(mm, (uintptr_t)code_store, sizeof(int), 1)) {
            return -E_INVAL;
        }
    }
    // 定义指向进程结构的指针以及布尔变量用于控制流程
    struct proc_struct *proc;
    bool intr_flag, haskid;
repeat:
// 初始化haskid为0，表示尚未找到可等待的子进程
    haskid = 0;

    // 如果pid不为0，寻找指定的子进程
    if (pid != 0) {
        proc = find_proc(pid);
        if (proc != NULL && proc->parent == current) {
            haskid = 1;
            if (proc->state == PROC_ZOMBIE) {
                goto found;
            }
        }
    }
    // 如果pid为0，遍历所有子进程寻找已终止的子进程
    else {
        proc = current->cptr;
        for (; proc != NULL; proc = proc->optr) {
            haskid = 1;
            if (proc->state == PROC_ZOMBIE) {
                goto found;
            }
        }
    }
    // 如果有子进程但都不是已终止状态，当前进程进入睡眠状态等待
    if (haskid) {
        current->state = PROC_SLEEPING;
        current->wait_state = WT_CHILD;
        schedule();
        if (current->flags & PF_EXITING) {
            do_exit(-E_KILLED);
        }
        goto repeat;
    }
    // 如果没有子进程可等待，返回错误代码
    return -E_BAD_PROC;

found:
// 确保找到的进程不是idleproc或initproc，因为它们不应该被等待
    if (proc == idleproc || proc == initproc) {
        panic("wait idleproc or initproc.\n");
    }
    // 如果需要，存储子进程的退出状态码到code_store指向的内存位置
    if (code_store != NULL) {
        *code_store = proc->exit_code;
    }
    // 保存中断标志并临时禁用中断，以确保操作的原子性
    local_intr_save(intr_flag);
    {
         // 从进程哈希表中移除进程，并移除其与其他进程的链接
        unhash_proc(proc);
        remove_links(proc);
    }
    // 恢复中断状态
    local_intr_restore(intr_flag);
    // 释放进程的内核栈和内存资源
    put_kstack(proc);
    kfree(proc);
    // 成功完成等待操作，返回0
    return 0;
}

// do_kill - kill process with pid by set this process's flags with PF_EXITING
int
do_kill(int pid) {
    struct proc_struct *proc;
    if ((proc = find_proc(pid)) != NULL) {
        if (!(proc->flags & PF_EXITING)) {
            proc->flags |= PF_EXITING;
            if (proc->wait_state & WT_INTERRUPTED) {
                wakeup_proc(proc);
            }
            return 0;
        }
        return -E_KILLED;
    }
    return -E_INVAL;
}

// kernel_execve - do SYS_exec syscall to exec a user program called by user_main kernel_thread
/**
 * 执行一个新程序
 * 
 * 该函数通过触发系统调用来执行一个新的程序它将程序的名称、二进制代码及其大小
 * 作为参数传递给操作系统，以便加载并执行新程序
 * 
 * @param name 程序的名称，用于标识要执行的程序
 * @param binary 程序的二进制代码，将被加载到内存中执行
 * @param size 二进制代码的大小，以字节为单位
 * 
 * @return 返回系统调用的结果如果成功，通常返回0；如果失败，返回错误码
 * 
 * 注意：该函数使用内联汇编来触发系统调用，这是一种低级操作，需要对系统调用和汇编语言有深入的了解
 */
static int
kernel_execve(const char *name, unsigned char *binary, size_t size) {
    int ret, len = strlen(name);
    asm volatile (
        "int %1;"
        : "=a" (ret)
        : "i" (T_SYSCALL), "0" (SYS_exec), "d" (name), "c" (len), "b" (binary), "D" (size)
        : "memory");
    return ret;
}

#define __KERNEL_EXECVE(name, binary, size) ({                          \
            cprintf("kernel_execve: pid = %d, name = \"%s\".\n",        \
                    current->pid, name);                                \
            kernel_execve(name, binary, (size_t)(size));                \
        })

#define KERNEL_EXECVE(x) ({                                             \
            extern unsigned char _binary_obj___user_##x##_out_start[],  \
                _binary_obj___user_##x##_out_size[];                    \
            __KERNEL_EXECVE(#x, _binary_obj___user_##x##_out_start,     \
                            _binary_obj___user_##x##_out_size);         \
        })

#define __KERNEL_EXECVE2(x, xstart, xsize) ({                           \
            extern unsigned char xstart[], xsize[];                     \
            __KERNEL_EXECVE(#x, xstart, (size_t)xsize);                 \
        })

#define KERNEL_EXECVE2(x, xstart, xsize)        __KERNEL_EXECVE2(x, xstart, xsize)

// user_main - kernel thread used to exec a user program
static int
user_main(void *arg) {
#ifdef TEST
    KERNEL_EXECVE2(TEST, TESTSTART, TESTSIZE);
#else
    KERNEL_EXECVE(hello);
#endif
    panic("user_main execve failed.\n");
}

// init_main - the second kernel thread used to create user_main kernel threads
/**
 * 初始化主函数，负责初始化系统并启动用户模式进程
 * 
 * 该函数首先保存当前空闲页面和内核分配的内存状态，作为后续比较的基准
 * 然后创建一个内核线程来执行用户主函数(user_main)如果创建失败，系统将陷入恐慌状态
 * 
 * 接着，函数进入一个循环，通过调度(scheduling)和等待(waiting)机制来管理进程
 * 直到所有用户模式进程结束
 * 
 * 最后，函数进行一系列断言/assertions来确保系统状态符合预期，包括：
 * 1. 当前进程的某些指针为空，表明没有其他进程挂载在当前进程上
 * 2. 进程数量为2，通常是一个空闲进程和当前初始化进程
 * 3. 进程列表的头尾指针正确地指向初始化进程的链表节点
 * 
 * 如果所有检查通过，函数将打印内存检查通过的消息并返回0
 * 
 * @param arg 传递给函数的参数，在此函数中未使用
 * @return 返回0表示成功执行
 */
static int
init_main(void *arg) {
    // 保存当前空闲页面数量和内核已分配内存大小，用于后续检查
    size_t nr_free_pages_store = nr_free_pages();
    size_t kernel_allocated_store = kallocated();

    // 创建内核线程来执行用户主函数，如果创建失败，则系统陷入恐慌
    int pid = kernel_thread(user_main, NULL, 0);
    if (pid <= 0) {
        panic("create user_main failed.\n");
    }

    // 循环等待和调度，直到所有用户模式进程结束
    while (do_wait(0, NULL) == 0) {
        schedule();
    }

    // 所有用户模式进程结束后，打印消息
    cprintf("all user-mode processes have quit.\n");

    // 断言系统状态符合预期
    assert(initproc->cptr == NULL && initproc->yptr == NULL && initproc->optr == NULL);
    assert(nr_process == 2);
    assert(list_next(&proc_list) == &(initproc->list_link));
    assert(list_prev(&proc_list) == &(initproc->list_link));

    // 如果所有检查通过，打印内存检查通过的消息
    cprintf("init check memory pass.\n");
    return 0;
}

// proc_init - set up the first kernel thread idleproc "idle" by itself and 
//           - create the second kernel thread init_main
void
proc_init(void) {
    int i;

    // 初始化全局进程列表
    list_init(&proc_list);

    // 初始化哈希列表，用于快速查找进程
    for (i = 0; i < HASH_LIST_SIZE; i ++) {
        list_init(hash_list + i);
    }

    // 分配空闲进程idleproc，这是系统中的第一个进程
    if ((idleproc = alloc_proc()) == NULL) {
        panic("cannot alloc idleproc.\n");
    }

    // 设置idleproc的基本信息
    idleproc->pid = 0;
    idleproc->state = PROC_RUNNABLE;
    idleproc->kstack = (uintptr_t)bootstack;
    idleproc->need_resched = 1;
    set_proc_name(idleproc, "idle");
    nr_process ++;

    // 将当前进程设置为idleproc
    current = idleproc;

    // 创建初始化进程init_main，这是系统中的第二个进程
    int pid = kernel_thread(init_main, NULL, 0);
    if (pid <= 0) {
        panic("create init_main failed.\n");
    }

    // 查找并设置初始化进程initproc
    initproc = find_proc(pid);
    set_proc_name(initproc, "init");

    // 断言确保idleproc和initproc正确初始化
    assert(idleproc != NULL && idleproc->pid == 0);
    assert(initproc != NULL && initproc->pid == 1);
}

// cpu_idle - at the end of kern_init, the first kernel thread idleproc will do below works
void
cpu_idle(void) {
    while (1) {
        //检查当前进程是否需要重新调度
        if (current->need_resched) {
            schedule();
        }
    }
}

